﻿using System;
using System.Collections.Generic;
using System.Data;

namespace org.se.fs.model
{
    public sealed class DatabaseManager : IDatabaseManager
    {
        public DatabaseManager(ProviderType providerType, String connectionString)
        {
            ProviderType = providerType;
            ConnectionString = connectionString;
        }

        public IDbConnection Connection { get; private set; }
        public IDataReader DataReader { get; set; }
        public ProviderType ProviderType { get; set; }
        public string ConnectionString { get; set; }
        public IDbCommand Command { get; private set; }
        public IDbTransaction Transaction { get; private set; }
        public IDbDataParameter[] Parameters { get; private set; }
        
        public void Open()
        {
            Connection = DatabaseFactory.GetConnection(ProviderType);
            Connection.ConnectionString = ConnectionString;
            if (Connection.State != ConnectionState.Open)
                Connection.Open();
            Command = DatabaseFactory.GetCommand(ProviderType);
        }

        public Boolean Ping()
        {
            try
            {
                Connection = DatabaseFactory.GetConnection(ProviderType);
                Connection.ConnectionString = ConnectionString;
                if (Connection.State != ConnectionState.Open)
                    Connection.Open();
                Connection.Close();
            }
            catch (Exception)
            {
                return false;        
            }
            return true;            
        }

        public void Close()
        {
            if (Connection.State != ConnectionState.Closed)
                Connection.Close();
        }

        public void Dispose()
        {
            GC.SuppressFinalize(this);
            Close();
            Command = null;
            Transaction = null;
            Connection = null;
        }

        public void CreateParameters(int paramsCount)
        {
            Parameters = new IDbDataParameter[paramsCount];
            Parameters = DatabaseFactory.GetParameters(ProviderType, paramsCount);
        }

        public void AddParameters(int index, String paramName, object objValue)
        {
            if (index >= Parameters.Length) return;
            Parameters[index].ParameterName = paramName;
            Parameters[index].Value = objValue;
        }

        public void BeginTransaction()
        {
            if (Transaction == null)
                Transaction = DatabaseFactory.GetTransaction(ProviderType);
            Command.Transaction = Transaction;
        }

        public void CommitTransaction()
        {
            if (Transaction != null)
                Transaction.Commit();
            Transaction = null;
        }

        public IDataReader ExecuteReader(CommandType commandType, String commandText)
        {
            Command = DatabaseFactory.GetCommand(ProviderType);
            Command.Connection = Connection;
            PrepareCommand(Command, Connection, Transaction, commandType, commandText, Parameters);
            DataReader = Command.ExecuteReader();
            Command.Parameters.Clear();
            return DataReader;
        }

        public void CloseReader()
        {
            if (DataReader != null)
                DataReader.Close();
        }

        private static void AttachParameters(IDbCommand command, IEnumerable<IDbDataParameter> commandParameters)
        {
            foreach (var idbParameter in commandParameters)
            {
                if ((idbParameter.Direction == ParameterDirection.InputOutput)
                    &&
                    (idbParameter.Value == null))
                {
                    idbParameter.Value = DBNull.Value;
                }
                command.Parameters.Add(idbParameter);
            }
        }

        private static void PrepareCommand(IDbCommand command, IDbConnection connection, IDbTransaction transaction, CommandType commandType, String commandText, IEnumerable<IDbDataParameter> commandParameters)
        {
            command.Connection = connection;
            command.CommandText = commandText;
            command.CommandType = commandType;

            if (transaction != null)
            {
                command.Transaction = transaction;
            }

            if (commandParameters != null)
            {
                AttachParameters(command, commandParameters);
            }
        }

        public int ExecuteNonQuery(CommandType commandType, String commandText)
        {
            Command = DatabaseFactory.GetCommand(ProviderType);
            PrepareCommand(Command, Connection, Transaction,
                           commandType, commandText, Parameters);
            var returnValue = Command.ExecuteNonQuery();
            Command.Parameters.Clear();
            return returnValue;
        }

        public object ExecuteScalar(CommandType commandType, String commandText)
        {
            Command = DatabaseFactory.GetCommand(ProviderType);
            PrepareCommand(Command, Connection, Transaction, commandType, commandText, Parameters);
            var returnValue = Command.ExecuteScalar();
            Command.Parameters.Clear();
            return returnValue;
        }

        public DataSet ExecuteDataSet(CommandType commandType, String commandText)
        {
            Command = DatabaseFactory.GetCommand(ProviderType);
            PrepareCommand(Command, Connection, Transaction, commandType, commandText, Parameters);
            var dataAdapter = DatabaseFactory.GetDataAdapter(ProviderType);
            dataAdapter.SelectCommand = Command;
            var dataSet = new DataSet();
            dataAdapter.Fill(dataSet);
            Command.Parameters.Clear();
            return dataSet;
        }
    }
}